package com.rio.dandan.view;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.servlet.view.AbstractView;

import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

/**
 * 
 * JsonView.java
 * 
 * @author 宋陆
 * @version 1.0 2014年5月18日
 */
public class JsonView extends AbstractView {

    /** The Constant log. */
    private static final Logger log = LoggerFactory.getLogger(JsonView.class);

    /** The Constant JSON_SerializeNulls. */
    public static final String JSON_SerializeNulls = "JsonView.JSON_SerializeNulls";

    /** The Constant JSON_WithoutExpose. */
    public static final String JSON_WithoutExpose = "JsonView.JSON_excludesFieldsWithoutExpose";

    /** The Constant DEFAULT_CONTENT_TYPE. */
    public static final String DEFAULT_CONTENT_TYPE = "application/json";

    /** The Constant EMPTY_JSON. */
    private static final String EMPTY_JSON = "{}";

    /** The Constant EMPTY_JSON_ARRAY. */
    private static final String EMPTY_JSON_ARRAY = "[]";

    /** The model keys. */
    private Set<String> modelKeys;

    /** The disable caching. */
    private boolean disableCaching = true;

    /** The extract value from single key model. */
    private boolean extractValueFromSingleKeyModel = false;

    /** The date pattern. */
    private String datePattern = "yyyy-MM-dd HH:mm:ss";

    /** The default excludes fields without expose. */
    private boolean defaultExcludesFieldsWithoutExpose = true;

    /** The default serialize nulls. */
    private boolean defaultSerializeNulls = false;

    /** The pretty printing. */
    private boolean prettyPrinting = false;

    /** The default builder. */
    private GsonBuilder defaultBuilder = new GsonBuilder();

    /** The exclusion strategy. */
    private ExclusionStrategy exclusionStrategy = new ExclusionStrategy() {
        public boolean shouldSkipField(FieldAttributes f) {
            return "hashCode".equals(f.getName());
        }

        public boolean shouldSkipClass(Class<?> clazz) {
            return false;
        }
    };

    /**
     * Instantiates a new json view.
     */
    public JsonView() {
        setContentType(DEFAULT_CONTENT_TYPE);
        setExposePathVariables(false);
        defaultBuilder.excludeFieldsWithoutExposeAnnotation();
        defaultBuilder.setDateFormat(datePattern);
        defaultBuilder.setExclusionStrategies(exclusionStrategy);
        if (prettyPrinting)
            defaultBuilder.setPrettyPrinting();
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.springframework.web.servlet.view.AbstractView#prepareResponse(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) {
        response.setContentType(getContentType());
        response.setCharacterEncoding("UTF-8");
        if (this.disableCaching) {
            response.addHeader("Pragma", "no-cache");
            response.addHeader("Cache-Control", "no-cache, no-store, max-age=0");
            response.addDateHeader("Expires", 1L);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.springframework.web.servlet.view.AbstractView#renderMergedOutputModel(java.util.Map, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
        boolean isSerializeNulls = defaultSerializeNulls;
        boolean excludesFieldsWithoutExpose = defaultExcludesFieldsWithoutExpose;

        Object nulls = model.get(JSON_SerializeNulls);
        Object expose = model.get(JSON_WithoutExpose);
        if (nulls != null && nulls instanceof Boolean) {
            isSerializeNulls = (Boolean) nulls;
        }
        if (expose != null && expose instanceof Boolean) {
            excludesFieldsWithoutExpose = (Boolean) expose;
        }

        Object value = filterModel(model);
        PrintWriter out = null;
        try {
            out = response.getWriter();
            out.write(toJson(value, isSerializeNulls, excludesFieldsWithoutExpose));
        } catch (IOException ex) {
            log.error(ex.getMessage());
        } finally {
            if (out != null) {
                out.flush();
                out.close();
            }
        }
    }

    /**
     * Filter model.
     * 
     * @param model
     *            the model
     * @return the object
     */
    protected Object filterModel(Map<String, Object> model) {
        Map<String, Object> result = new HashMap<String, Object>(model.size());
        Set<String> renderedAttributes = (!CollectionUtils.isEmpty(this.modelKeys) ? this.modelKeys : model.keySet());
        renderedAttributes.remove(JSON_SerializeNulls);
        renderedAttributes.remove(JSON_WithoutExpose);
        for (Map.Entry<String, Object> entry : model.entrySet()) {
            if (entry.getValue() instanceof Exception)
                return entry.getValue();
            if (!(entry.getValue() instanceof BindingResult) && renderedAttributes.contains(entry.getKey())) {
                result.put(entry.getKey(), entry.getValue());
            }
        }
        return (extractValueFromSingleKeyModel && result.size() == 1 ? result.values().iterator().next() : result);
    }

    /**
     * To json.
     * 
     * @param target
     *            the target
     * @param isSerializeNulls
     *            the is serialize nulls
     * @param excludesFieldsWithoutExpose
     *            the excludes fields without expose
     * @return the string
     */
    private String toJson(Object target, boolean isSerializeNulls, boolean excludesFieldsWithoutExpose) {
        if (target == null)
            return EMPTY_JSON;
        if (target instanceof Exception) {
//            log.error(((Exception) target).getMessage());
//            return EMPTY_JSON;
            throw new RuntimeException((Exception) target);
        }

        Gson gson;
        if (!isSerializeNulls && excludesFieldsWithoutExpose) {
            gson = defaultBuilder.create();
        } else {
            GsonBuilder builder = new GsonBuilder();
            if (prettyPrinting)
                builder.setPrettyPrinting();
            builder.setExclusionStrategies(exclusionStrategy);
            builder.setDateFormat(datePattern);
            if (isSerializeNulls)
                builder.serializeNulls();
            if (excludesFieldsWithoutExpose)
                builder.excludeFieldsWithoutExposeAnnotation();
            gson = builder.create();
        }

        String result = "";
        try {
            result = gson.toJson(target);
        } catch (Exception ex) {
            log.error(ex.getMessage());
            if (target instanceof Collection || target instanceof Iterator || target instanceof Enumeration || target.getClass().isArray()) {
                result = EMPTY_JSON_ARRAY;
            } else
                result = EMPTY_JSON;
        }
        return result;
    }

    /**
     * Sets the date pattern.
     * 
     * @param datePattern
     *            the new date pattern
     */
    public void setDatePattern(String datePattern) {
        this.datePattern = datePattern;
    }

    /**
     * Checks if is extract value from single key model.
     * 
     * @return true, if checks if is extract value from single key model
     */
    public boolean isExtractValueFromSingleKeyModel() {
        return extractValueFromSingleKeyModel;
    }

    /**
     * Sets the extract value from single key model.
     * 
     * @param extractValueFromSingleKeyModel
     *            the extract value from single key model
     */
    public void setExtractValueFromSingleKeyModel(boolean extractValueFromSingleKeyModel) {
        this.extractValueFromSingleKeyModel = extractValueFromSingleKeyModel;
    }

    /**
     * Sets the default excludes fields without expose.
     * 
     * @param defaultExcludesFieldsWithoutExpose
     *            the default excludes fields without expose
     */
    public void setDefaultExcludesFieldsWithoutExpose(boolean defaultExcludesFieldsWithoutExpose) {
        this.defaultExcludesFieldsWithoutExpose = defaultExcludesFieldsWithoutExpose;
    }

    /**
     * Sets the default serialize nulls.
     * 
     * @param defaultSerializeNulls
     *            the default serialize nulls
     */
    public void setDefaultSerializeNulls(boolean defaultSerializeNulls) {
        this.defaultSerializeNulls = defaultSerializeNulls;
    }

    /**
     * Sets the pretty printing.
     * 
     * @param prettyPrinting
     *            the pretty printing
     */
    public void setPrettyPrinting(boolean prettyPrinting) {
        this.prettyPrinting = prettyPrinting;
    }

}
